﻿/********************************************************************************
** 类名称： CacheHelper
** 描述： 缓存帮助类
** 作者： xjh
** 创建时间：2019-5-3
** 最后修改人：
** 最后修改时间：
** 版权所有 (C) :HisPro
*********************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;

namespace HCenter.CacheHelper
{
    /// <summary>
    /// 缓存帮助类
    /// </summary>
    public class MemoryCacheImp : ICacheProvider
    {
        #region var
        private MemoryCache cache = null;
        
        /// <summary>
        /// 获取缓存的名称
        /// </summary>
        public string Name { get { return cache != null ? cache.Name : this.GetType().Name; } }
        /// <summary>
        /// 获取缓存可使用的物理内存的百分比
        /// </summary>
        public long PhysicalMemoryLimit { get { return cache != null ? cache.PhysicalMemoryLimit : 0; } }
        
        /// <summary>
        /// 获取计算机上缓存可使用的内存量（以字节为单位）
        /// </summary>
        public long CacheMemoryLimit { get { return cache != null ? cache.CacheMemoryLimit : 0; } }
        /// <summary>
        /// 缓存中的缓存项总数
        /// </summary>
        public long Count { get { return cache != null ? cache.GetCount() : 0; } }

        public string KeySuffix { get; set; }
        private readonly object lockobj = new object();
        #endregion

        #region MemoryCacheImp
        private static MemoryCacheImp _Instance = null;
        public static MemoryCacheImp Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new MemoryCacheImp();
                return _Instance;
            }
        }

        public MemoryCacheImp(string name = "")
        {
            if (name.IsNullOrEmpty())
                name = this.GetType().Name;
            cache = new MemoryCache(name);
        }
        #endregion

        #region ContainsKey
        /// <summary>
        /// 确定缓存中是否存在某个缓存项
        /// </summary>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <returns></returns>
        public bool ContainsKey(string key)
        {
            key = GetKeySuffix(key);
            return cache.Contains(key);
        }
        #endregion

        #region Set
        /// <summary>
        /// 新增或更新缓存
        /// </summary>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <param name="value">缓存项的数据</param>
        /// <param name="expirationMinutes">缓存时长(分钟)</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        public bool Set(string key, object value, int? expirationMinutes = 30, bool isSliding = true)
        {
            if (value.IsNullOrEmpty())
                throw new ArgumentNullException(nameof(value));
            lock (lockobj)
            {
                var cacheItemPolicy = CreatePolicy(expirationMinutes, isSliding);
                cache.Set(GetKeySuffix(key), value, cacheItemPolicy);

                return ContainsKey(key);
            }
        }
        /// <summary>
        /// 新增或更新缓存
        /// </summary>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <param name="value">缓存项的数据</param>
        /// <param name="expiresTime">缓存时长(分钟), enum ExpiresTime</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        public bool Set(string key, object value, ExpiresTime expiresTime = ExpiresTime.Minutes_30, bool isSliding = true)
        {
            int expirationMinutes = expiresTime.GetHashCode();
            return Set(key, value, expirationMinutes, isSliding);
        }

        public bool Set<T>(string key, T value, TimeSpan? expireTime = null)
        {
            int? expirationMinutes = expireTime != null && expireTime.HasValue ? Convert.ToInt32(expireTime.Value.TotalSeconds) : 0;
            return Set(key, value, expirationMinutes, false);
        }
        public bool Exists(string key) { return ContainsKey(key); }
        #endregion

        #region Remove
        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            //if (ContainsKey(key))
            //{
            lock (lockobj)
            {
                var obj = cache.Remove(GetKeySuffix(key));
                try
                {
                    if (obj != null && (obj.GetType().IsClass || obj is System.Collections.IEnumerable))
                        obj = null;
                }
                catch { }
                return obj != null;
            }            
            //return false;
        }

        ///// <summary>
        ///// 批量删除缓存
        ///// </summary>
        ///// <param name="keys">缓存Keys</param>
        //public void Remove(System.Collections.IEnumerable keys)
        //{
        //    if (keys == null)
        //        return;

        //    keys.CastToList<string>().ForEach(item => Remove(item));
        //}
        /// <summary>
        /// 删除多个key
        /// </summary>
        /// <param name="keys">rediskey</param>
        /// <returns>成功删除的个数</returns>
        public long Remove(List<string> keys)
        {
            if (keys == null)
                return 0;
            keys.ForEach(item => Remove(item));
            return keys.Count;
        }
        /// <summary>
        /// 删除所有缓存
        /// </summary>
        public bool RemoveAll()
        {
            var keys = GetKeys();
            foreach (var key in keys)
            {
                Remove(key);
            }
            return true;
        }
        #endregion

        #region Get
        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <param name="cachePopulate">>获取缓存值的操作(当缓存不存在相应的数据时，执行此方法获取数据)</param>
        /// <param name="expiresTime">缓存时长(分钟), enum ExpiresTime</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        public T Get<T>(string key, Func<T> cachePopulate, ExpiresTime expiresTime = ExpiresTime.Minutes_30, bool isSliding = true, Func<T,bool> checkPopulate=null)
        {
            var cacheItem = cache.GetCacheItem(GetKeySuffix(key));
            if (!cacheItem.IsNullOrEmpty() && cacheItem.Value is T value)
            {
                return value;
            }
            else if (cachePopulate != null)//调用fucloadData获取数据
            {
                var val = cachePopulate();
                if (!val.IsNullOrEmpty())//缓存
                {
                    if (checkPopulate == null || checkPopulate?.Invoke(val) == true)
                        Set(key, val, expiresTime, isSliding);
                    return val;
                }
            }
            return default(T);
        }
        public T Get<T>(string key, Func<T> cachePopulate, TimeSpan? expiresTime, Func<T, bool> checkPopulate = null)
        {
            var cacheItem = cache.GetCacheItem(GetKeySuffix(key));
            if (!cacheItem.IsNullOrEmpty() && cacheItem.Value is T value)
            {
                return value;
            }
            else if (cachePopulate != null)//调用fucloadData获取数据
            {
                var val = cachePopulate();
                if (!val.IsNullOrEmpty())//缓存
                {
                    if (checkPopulate == null || checkPopulate?.Invoke(val) == true)
                        Set(key, val, expiresTime);
                    return val;
                }
            }
            return default(T);
        }
        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            var cacheItem = Get(key);// cache.GetCacheItem(GetKeySuffix(key));
            if (!cacheItem.IsNullOrEmpty() && cacheItem is T value)
            {
                return value;
            }
            return default(T);
        }
        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存项的唯一标识符</param>
        /// <returns></returns>
        public object Get(string key)
        {
            var cacheItem = cache.Get(GetKeySuffix(key));
            if (!cacheItem.IsNullOrEmpty())
            {
                return cacheItem;
            }
            return null;
        } // <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryGet(string key, out object val)
        {
            val = this.Get(key);
            return !val.IsNullOrEmpty();
        }
        // <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryGet<T>(string key, out T val)
        {
            val = this.Get<T>(key);
            return !val.IsNullOrEmpty();
        }
        #endregion

        #region Hash
        public bool HashSet<T>(string key, string fieldKey, T fieldValue)
        {
            return HashSet<T>(key, new Dictionary<string, T> { { fieldKey, fieldValue } });
        }

        public bool HashSet<T>(string key, Dictionary<string, T> dict)
        {
            if (dict == null || dict.Count == 0) return false;
            lock (lockobj)
            {
                if (!TryGet(key, out Dictionary<string, object> hashFields))
                {
                    hashFields = new Dictionary<string, object>();
                }
                foreach (var kv in dict)
                {
                    hashFields[kv.Key] = kv.Value;
                }
                return Set(key, hashFields);
            }
        }

        public T HashGet<T>(string key, string fieldKey)
        {
            var dict = HashGet<T>(key, new Dictionary<string, T> { { fieldKey, default(T) } });
            return dict[fieldKey];
        }

        public Dictionary<string, T> HashGet<T>(string key, Dictionary<string, T> dict)
        {
            if (dict == null || dict.Count == 0) return dict;
            if (TryGet(key, out Dictionary<string, object> hashFields))
            {
                //var hashFields = Get<Dictionary<string, T>>(key);
                foreach (var keyValuePair in hashFields.Where(p => dict.Keys.Contains(p.Key)))
                {
                    if (keyValuePair.Value is T val)
                        dict[keyValuePair.Key] = val;
                }
            }
            return dict;
        }

        public Dictionary<string, T> HashGet<T>(string key)
        {
            Dictionary<string, T> dict = new Dictionary<string, T>();
            if (TryGet(key, out Dictionary<string, object> hashFields))
            {
                //var hashFields = Get<Dictionary<string, T>>(key);
                foreach (string field in hashFields.Keys)
                {
                    if (hashFields[field] is T val)
                        dict[field] = val;
                }
            }
            return dict;
        }

        /// <summary>
        /// 获取dataKeys所有Value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public List<T> HashGet<T>(string key, List<string> dataKeys)
        {
            List<T> list = new List<T>();
            if (dataKeys == null || dataKeys.Count == 0) return list;
            if (TryGet(key, out Dictionary<string, object> hashFields))
            {
                //var hashFields = Get<Dictionary<string, T>>(key);
                foreach (var keyValuePair in hashFields.Where(p => dataKeys.Contains(p.Key)))
                {
                    if (keyValuePair.Value is T val)
                        list.Add(val);
                }
            }
            return list;
        }

        public Dictionary<string, T> HashGetAll<T>(string key)
        {
            Dictionary<string, T> list = new Dictionary<string, T>();
            if (TryGet(key, out Dictionary<string, object> hashFields))
            {
                foreach (var val in hashFields)
                {
                    if (val.Value is T vals)
                        list[val.Key] = vals;
                }
            }
            return list;
        }

        public bool RemoveHash(string key, string fieldKey)
        {
            Dictionary<string, bool> dict = new Dictionary<string, bool> { { fieldKey, false } };
            dict = RemoveHash(key, dict);
            return dict[fieldKey];
        }

        public Dictionary<string, bool> RemoveHash(string key, Dictionary<string, bool> dict)
        {
            if (dict == null || dict.Count == 0) return dict;
            lock (lockobj)
            {
                if (TryGet(key, out Dictionary<string, object> hashFields))
                {
                    //var hashFields = Get<Dictionary<string, object>>(key);
                    foreach (string fieldKey in dict.Keys)
                    {
                        dict[fieldKey] = hashFields.Remove(fieldKey);
                    }
                }
                return dict;
            }
        }
        public bool HashExists(string key, string fieldKey)
        {
            Dictionary<string, bool> dict = new Dictionary<string, bool> { { fieldKey, false } };
            dict = HashExists(key, dict);
            return dict[fieldKey];
        }
        public Dictionary<string, bool> HashExists(string key, Dictionary<string, bool> dict)
        {
            if (dict == null || dict.Count == 0) return dict;

            if (TryGet(key, out Dictionary<string, object> hashFields))
            {
                //var hashFields = Get<Dictionary<string, object>>(key);
                foreach (string fieldKey in dict.Keys)
                {
                    dict[fieldKey] = hashFields.ContainsKey(fieldKey);
                }
            }
            return dict;
        }

        /// <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dataKey"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGet<T>(string key, string dataKey, out T val)
        {
            val = this.HashGet<T>(key, dataKey);
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 从redis获取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dataKey"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGet<T>(string key, List<string> dataKey, out List<T> val)
        {
            val = default(List<T>);
            if (dataKey?.Count > 0)
            {
                val = HashGet<T>(key, dataKey);
            }
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 获取hashkey所有Redis key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGetAll<T>(string key, out Dictionary<string, T> val)
        {
            val = HashGetAll<T>(key);
            if (!val.IsNullOrEmpty()) return true;
            return !val.IsNullOrEmpty();
        }
        /// <summary>
        /// 获取hashkey所有cache key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool TryHashGetAll<T>(string key, out List<T> val)
        {
            val = default(List<T>);
            if (this.TryHashGetAll<T>(key, out Dictionary<string, T> dicval) == true)
            {
                val = dicval.Values.ToList();
            }
            return !val.IsNullOrEmpty();
        }
        //-------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// 批量删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="listdataKey"></param>
        /// <returns></returns>
        public bool HashDeleteExecute(string key, List<string> listdataKey)
        {
            if (listdataKey == null || listdataKey.Count == 0) return false;

            Dictionary<string, bool> dict = new Dictionary<string, bool>();
            foreach (var fieldKey in listdataKey)
            {
                dict[fieldKey] = false;
            }
            RemoveHash(key, dict);
            return true;
        }
        /// <summary>
        /// 批量删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="listdataKey"></param>
        /// <returns></returns>
        public bool HashDeleteExecute(Dictionary<string, List<string>> listdataKey)
        {
            if (listdataKey == null || listdataKey.Count == 0) return false;
            foreach (var dic in listdataKey)
            {
                var ret = HashDeleteExecute(dic.Key, dic.Value);
            }
            return true;
        }
        /// <summary>
        /// 批量更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="DellistdataKey">批量删除</param>
        /// <param name="dicHashEntrys">批量保存</param>
        /// <returns></returns>
        public bool HashUpdateExecute<T>(Dictionary<string, List<string>> DellistdataKey, Dictionary<string, Dictionary<string, T>> dicHashEntrys)
        {
            if (DellistdataKey == null && DellistdataKey.Count == 0 && dicHashEntrys == null && dicHashEntrys.Count == 0) return false;

            if (DellistdataKey?.Count > 0)//删除
            {
                HashDeleteExecute(DellistdataKey);
            }
            if (dicHashEntrys?.Count > 0)//更新
            {
                return HashSetExecute(dicHashEntrys);
            }
            return true;
        }
        /// <summary>
        /// 批量保存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="dicHashEntrys"></param>
        /// <returns></returns>
        public bool HashSetExecute<T>(string key, Dictionary<string, T> dicHashEntrys)
        {
            if (dicHashEntrys == null || dicHashEntrys.Count == 0) return false;
            return HashSet(key, dicHashEntrys);
        }
        /// <summary>
        /// 批量保存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dicHashEntrys"></param>
        /// <returns></returns>
        public bool HashSetExecute<T>(Dictionary<string, Dictionary<string, T>> dicHashEntrys)
        {
            if (dicHashEntrys == null || dicHashEntrys.Count == 0) return false;
            foreach (var dic in dicHashEntrys)
            {
                HashSetExecute(dic.Key, dic.Value);
            }
            return true;
        }
        #endregion

        #region LockTake
        public bool LockRelease(string key, string value)
        {
            //key = GetKeySuffix(key);
            var lockName = key + "_lock"; //unique lock name. key-relative.
            return Remove(lockName);
        }
        public bool LockTake(string key, string value, TimeSpan? expiry)
        {
            //key = GetKeySuffix(key);
            var lockName = key + "_lock"; //unique lock name. key-relative.
            var lockToken = (value.IsNullOrEmpty() ? value : Guid.NewGuid().ToString());

            if (Exists(lockName))
                return false;

            var rets = Set(lockName, lockToken, expiry ?? TimeSpan.FromSeconds(10));
            return rets;
        }

        public IDisposable AcquireLock(string key, TimeSpan timeOut, bool useLock = true)
        {
            return new CacheLock(this, key, timeOut, useLock);
        }
        #endregion

        #region GetKeys
        /// <summary>
        /// 获取所有缓存键
        /// </summary>
        /// <returns></returns>
        public List<string> GetKeys()
        {
            var keys = new List<string>();
            if (cache == null) return keys;
            foreach (KeyValuePair<string, object> cacheItem in cache)
            {
                keys.Add(cacheItem.Key);
            }
            return keys;
        }
        #endregion

        #region Trim
        /// <summary>
        /// 从缓存对象中移除指定百分比的缓存项
        /// </summary>
        /// <param name="percent">要移除的缓存项总数的百分比</param>
        /// <returns>从缓存中移除的项数</returns>
        public long Trim(int percent)
        {
            return cache != null ? cache.Trim(percent) : 0;
        }
        #endregion

        #region CreatePolicy
        /// <summary>
        /// 设置前缀
        /// </summary>
        /// <param name="customKey"></param>
        public void SetSysCustomKey(string customKey)
        {
            KeySuffix = customKey;
        }
        private string GetKeySuffix(string key)
        {
            checkKey(key);
            return string.IsNullOrEmpty(KeySuffix) ? key : string.Format("_{0}_{1}", KeySuffix, key);
        }
        private void checkKey(string key)
        {
            if (key.IsNullOrEmpty())
                throw new ArgumentNullException(nameof(key));
        }
        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="slidingExpiration">指示是否应在指定的持续时间之后逐出缓存项</param>
        /// <param name="absoluteExpiration">指示是否应在给定时段内未访问，就会对它进行是否逐出缓存项</param>
        /// <returns></returns>
        private CacheItemPolicy CreatePolicy(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
        {
            var policy = new CacheItemPolicy();

            if (absoluteExpiration.HasValue)
            {
                policy.AbsoluteExpiration = absoluteExpiration.Value;
            }
            else if (slidingExpiration.HasValue)
            {
                policy.SlidingExpiration = slidingExpiration.Value;
            }
            policy.Priority = CacheItemPriority.Default;

            return policy;
        }

        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="expiresIn">缓存时长</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        private CacheItemPolicy CreatePolicy(TimeSpan? expiresIn, bool isSliding = true)
        {
            if (isSliding)
            {
                return CreatePolicy(expiresIn,null);
            }
            else
            {
                DateTimeOffset? absoluteExpiration = null;
                if (expiresIn.HasValue)
                    absoluteExpiration = DateTimeOffset.Now.Add(expiresIn.Value);
                return CreatePolicy(null, absoluteExpiration);
            }
        }
        /// <summary>
        /// 创建一个表示一组特定的缓存项的逐出和有效期的详细信息
        /// </summary>
        /// <param name="expirationMinutes">缓存时长(分钟)</param>
        /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
        /// <returns></returns>
        private CacheItemPolicy CreatePolicy(int? expirationMinutes = 30, bool isSliding = true)
        {
            TimeSpan? expiresIn = null;
            if (expirationMinutes.HasValue && expirationMinutes.Value > 0)
                expiresIn = DateTimeOffset.Now.AddMinutes(expirationMinutes.Value) - DateTimeOffset.Now;
            return CreatePolicy(expiresIn, isSliding);
        }
        #endregion

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    RemoveAll();
                    // TODO: 释放托管状态(托管对象)。
                }
                disposedValue = true;
            }
        }
        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
